﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Text.RegularExpressions;
using System.Xml;
using System.Xml.XPath;
using System.IO;
using System.Windows.Forms;

namespace GateExtractor
{
    interface ILogger
    {
        void WriteLine(string str);
        void Write(string str);
   
    }
    class Cluster
    {
        public List<string> idsElements = new List<string>();
        public TreeNode treeNode;
        public Dictionary<string, double> GetAvgAttributes(Dictionary<string, Dictionary<string, double>> elements)
        {
            Dictionary<string, double> attrs = new Dictionary<string, double>();
            foreach (string key in idsElements)
            {
                foreach (KeyValuePair<string, double> kvpAttribute in elements[key])
                {
                    if (attrs.ContainsKey(kvpAttribute.Key))
                        attrs[kvpAttribute.Key] += kvpAttribute.Value / idsElements.Count;
                    else
                        attrs.Add(kvpAttribute.Key, kvpAttribute.Value / idsElements.Count);
                }
            }
            return attrs;
            
        }
        public string GetAvgAttributesStr(Dictionary<string, Dictionary<string, double>> elements)
        {
            Dictionary<string, double> attrs = GetAvgAttributes(elements);
            string strResult="";
            //dispersion

            
            IEnumerable<KeyValuePair<string,double>> queue;
            queue = attrs.OrderByDescending(pair => pair.Value);
            foreach (KeyValuePair<string, double> kvpAttribute in queue)
            {
                double sum = 0;
                double tempVal = 0;
                foreach (string key in idsElements)
                {
                    if (elements[key].ContainsKey(kvpAttribute.Key))
                        tempVal = elements[key][kvpAttribute.Key];
                    sum += (tempVal - kvpAttribute.Value) * (tempVal - kvpAttribute.Value);
                }
                double disp = sum / idsElements.Count;
                strResult += kvpAttribute.Key + "=" + kvpAttribute.Value.ToString("F4") + "(disp:"+disp.ToString("F3")+")   ";
            }

            
            return strResult;
        }
        public static double GetDistance(Cluster cl1, Cluster cl2, Dictionary<string, Dictionary<string, double>> elements) //euclidian
        {
            double dist = 0;
            List<string> attrNames= new List<string>();
            Dictionary<string, double> attrs1 = cl1.GetAvgAttributes(elements);
            Dictionary<string, double> attrs2 = cl2.GetAvgAttributes(elements);

            foreach (string key in attrs1.Keys)
                if (!attrNames.Contains(key)) attrNames.Add(key);
            foreach (string key in attrs2.Keys)
                if (!attrNames.Contains(key)) attrNames.Add(key);

            foreach (string key in attrNames)
            {
                double val1 = 0;
                if (attrs1.ContainsKey(key)) val1 = attrs1[key];
                double val2 = 0;
                if (attrs2.ContainsKey(key)) val2 = attrs2[key];
                dist += (val2 - val1)*(val2-val1);
                //dist += Math.Min(val1,val2);
            }
            return dist;
        }

    }

    class Processor
    {
        ILogger logger;
        public Processor(ILogger _logger)
        { logger = _logger; }

        public List<Cluster> clusters = new List<Cluster>();
        Dictionary<string, Dictionary<string, double>> elementsNormalized;





        public void Process()
        {
            logger.WriteLine("Tab clusterer 0.2");
            DateTime dt_start = DateTime.Now;

            //global init

            elementsNormalized = new Dictionary<string, Dictionary<string, double>>();

            DirectoryInfo di = new DirectoryInfo("./in");
            FileInfo[] Files = di.GetFiles();
            try
            {
                if (Directory.Exists("./out")) Directory.Delete("./out", true);
            }
            catch (Exception ex)
            {
                Console.WriteLine("./out :" + ex.Message);
            }
            try { Directory.CreateDirectory("./out"); }
            catch (Exception ex) { Console.WriteLine("./out :" + ex.Message); };

            foreach (FileInfo fi in Files)
            {
                ProcessFile(fi);
            }
            //global results
            Console.WriteLine("---------------Clustering----------");
            Clusterize();
        }
   
        void Clusterize()
        {
            TextWriter wr = new StreamWriter("./out/___clusters");
            
            //create singular classes
            foreach (KeyValuePair<string, Dictionary<string, double>> kvpPersons in elementsNormalized)
            {
                wr.Write(kvpPersons.Key + "\t" + kvpPersons.Value.Count + ":\t");
                string tooltip="";
                IEnumerable<KeyValuePair<string, double>> queue;
                queue = kvpPersons.Value.OrderBy(pair => pair.Key);

                foreach (KeyValuePair<string, double> kvpAttribute in queue)
                {
                    wr.Write(kvpAttribute.Key + "=" + kvpAttribute.Value.ToString("F4") + "\t");
                    tooltip+=kvpAttribute.Key + "=" + kvpAttribute.Value.ToString("F3") + "  ";
                }
                wr.WriteLine();
                Cluster newCluster = new Cluster();
                newCluster.idsElements.Add(kvpPersons.Key);
                newCluster.treeNode = new TreeNode(kvpPersons.Key);
                newCluster.treeNode.ToolTipText = tooltip;
                clusters.Add(newCluster);

            }
            wr.WriteLine();
            
            int pass = 0;
            while (clusters.Count>1)
            {
                //print current clusters
                wr.WriteLine("\n#######################      pass " + pass.ToString() + "      #########################");
                wr.WriteLine("clusters:");
                foreach (Cluster curCluster in clusters)
                {
                    wr.Write("*\t");
                    foreach (string key in curCluster.idsElements)
                    {
                        wr.Write(key + "   ");
                    }
                    wr.WriteLine();
                    // print average attributes
                    wr.WriteLine("\taverage attributes: " + curCluster.GetAvgAttributesStr(elementsNormalized));
                    wr.WriteLine();
                }
                //selecting closest clusters
                
                List<int> idxClustersToJoin= new List<int>();
                double distMin = Cluster.GetDistance(clusters[0], clusters[1], elementsNormalized);;
                idxClustersToJoin.Add(0);
                idxClustersToJoin.Add(1);

                for (int i = 0; i < clusters.Count - 1; i++)
                    for (int j = i + 1; j < clusters.Count; j++)
                    {
                        double dist = Cluster.GetDistance(clusters[i], clusters[j], elementsNormalized);
                        if (dist < distMin) //////aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa reverse distance
                        {
                            idxClustersToJoin[0] = i;
                            idxClustersToJoin[1] = j;
                            distMin = dist;
                        }
                    }
                for (int i = 0; i < clusters.Count ; i++)
                {
                        double dist = Cluster.GetDistance(clusters[i], clusters[idxClustersToJoin[0]], elementsNormalized);
                        if (dist == distMin)
                        {
                            if (!idxClustersToJoin.Contains(i)) idxClustersToJoin.Add(i);
                        }
                 }
            
                //selecting more then two cluster to join                
                
                //print clusters to join
                wr.WriteLine("will be joint:");
                foreach (int idx in idxClustersToJoin)
                {
                    wr.Write("*\t");
                    foreach (string key in clusters[idx].idsElements)
                    {
                        wr.Write(key + "  ");
                    }
                    wr.WriteLine();
                    wr.WriteLine(clusters[idx].GetAvgAttributesStr(elementsNormalized));
                    //write attrs
                    wr.WriteLine();
                }
                wr.WriteLine("dist=" + distMin);
                //new generation of  clusters:
                //copiing all clustes
                List<Cluster> newClusters = new List<Cluster>();
                for (int i=0;i<clusters.Count;i++) //moving old clusters
                {
                    if (!idxClustersToJoin.Contains(i))
                    {
                        newClusters.Add(clusters[i]);
                    }
                } 
                //merging two closest clusters
                Cluster mergedCluster = new Cluster();
                mergedCluster.treeNode = new TreeNode("*");
                foreach (int id in idxClustersToJoin)
                {
                    foreach (string key in clusters[id].idsElements)
                        mergedCluster.idsElements.Add(key);
                    mergedCluster.treeNode.Nodes.Add(clusters[id].treeNode);
                }
                mergedCluster.treeNode.Expand();
                mergedCluster.treeNode.ToolTipText =mergedCluster.GetAvgAttributesStr(elementsNormalized);
                
                
                newClusters.Add(mergedCluster);
                clusters = newClusters;
                pass++;
            }
            

            wr.Close();
        }
        void ProcessFile(FileInfo fi)
        {
            logger.WriteLine("\nProcessing " + fi.Name);
            //load elemetns from tab separated 
            TextReader tr1 = new StreamReader(fi.FullName);
            //read col names from the first row
            string strCSVHeader = tr1.ReadLine();
            logger.WriteLine(strCSVHeader);
            string[] tokensHeader = strCSVHeader.Split('\t');
            //create list of attribute names
            //read elements line by line
            string strTempLine;
            while ((strTempLine = tr1.ReadLine()) != null)
            {
                string[] tokensElement = strTempLine.Split('\t');
                string strElementName=tokensElement[0];
                elementsNormalized.Add(strElementName, new Dictionary<string, double>());
                string[] semsOfInterest = new string[] { "busyp", "happyp", "kindp", "moralp", "moralm", "consciousp", "lightm", "togetherm", "togetherp"};

                for (int i=1;i<tokensHeader.Count();i++)
                {
                    if (semsOfInterest.Contains(tokensHeader[i]))
                    elementsNormalized[strElementName].Add(tokensHeader[i],System.Convert.ToDouble(tokensElement[i]));
                }
                
            }
 
            tr1.Close();

            Dictionary<string, Dictionary<string, double>> elements; 

             elements = new Dictionary<string, Dictionary<string, double>>();
        }// end processfile
    }

}
